/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 *
 * Wytyczne dla edge:
 * + lacz tylko pin out z pin in.
 * + musisz zachowac kolejnosc out->in
 * + Sprawdz czy in nie jest polaczony z wieksza iloscia niz 1 out
 * + Warunkiem rozpoczecia obliczen danego CO jest to, ze zakncza sie wszystkie porpzedzajace obliczenia. Dlatego jezeli OUT wchodzi juz do IN danego obiektu, to jego OUT nie moze wychodzi do IN zrodla. Nie moze powstac petla
 */
package pl.put.dcs_modeler.visualModelEditor.scene;

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectInformation;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.visual.action.AcceptProvider;
import org.netbeans.api.visual.action.ActionFactory;
import org.netbeans.api.visual.action.ConnectProvider;
import org.netbeans.api.visual.action.ConnectorState;
import org.netbeans.api.visual.action.PopupMenuProvider;
import org.netbeans.api.visual.action.ReconnectProvider;
import org.netbeans.api.visual.action.WidgetAction;
import org.netbeans.api.visual.anchor.Anchor;
import org.netbeans.api.visual.anchor.AnchorFactory;
import org.netbeans.api.visual.graph.GraphPinScene;
import org.netbeans.api.visual.graph.layout.GridGraphLayout;
import org.netbeans.api.visual.layout.LayoutFactory;
import org.netbeans.api.visual.layout.SceneLayout;
import org.netbeans.api.visual.model.ObjectSceneEvent;
import org.netbeans.api.visual.model.ObjectSceneEventType;
import org.netbeans.api.visual.model.ObjectSceneListener;
import org.netbeans.api.visual.model.ObjectState;
import org.netbeans.api.visual.router.Router;
import org.netbeans.api.visual.router.RouterFactory;
import org.netbeans.api.visual.vmd.VMDColorScheme;
import org.netbeans.api.visual.vmd.VMDConnectionWidget;
import org.netbeans.api.visual.vmd.VMDFactory;
import org.netbeans.api.visual.vmd.VMDGraphScene;
import org.netbeans.api.visual.vmd.VMDNodeWidget;
import org.netbeans.api.visual.widget.ConnectionWidget;
import org.netbeans.api.visual.widget.EventProcessingType;
import org.netbeans.api.visual.widget.LayerWidget;
import org.netbeans.api.visual.widget.Scene;
import org.netbeans.api.visual.widget.Widget;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.Repository;
import org.openide.util.Exceptions;
import org.openide.util.Utilities;
import pl.put.dcs.core.utils.serializer.SerializeManager;
import pl.put.dcs_modeler.exceptions.dcsModelerIOException.DCSModelerObjectIOException;
import pl.put.dcs_modeler.fileTypeDataLoader.DCSComputingModelDataObject;
import pl.put.dcs_modeler.loaders.ComputingObjectLoader;

import pl.put.dcs_modeler.visualModelEditor.FlowProjectProperties;
import pl.put.model.exceptions.ComputingObjectExist;
import pl.put.dcs_modeler.visualModelEditor.propertiesEditor.editors.ModelPropertyEditor;
import pl.put.dcs_modeler.visualModelEditor.scene.widget.CFMNodeWidget;
import pl.put.dcs_modeler.visualModelEditor.scene.widget.CFMPinWidget;
import pl.put.dcs_modeler.visualModelEditor.scene.widget.cfmPinWidget.CFMPinInWidget;
import pl.put.dcs_modeler.visualModelEditor.scene.widget.cfmPinWidget.CFMPinOutWidget;
import pl.put.dcs_modeler.wizards.listComputingObjects.ListComputingObjectsWizardAction;
import pl.put.model.computingObject.ComputingObject;
import pl.put.model.computingObject.pin.Pin;
import pl.put.model.computingObject.pin.PinIn;
import pl.put.model.computingObject.pin.PinOut;
import pl.put.model.flowModel.FlowModel;
import pl.put.model.flowModel.SerializableFlowModel;
import pl.put.model.flowModel.connection.Connection;
import pl.put.model.flowModel.flowComputingObjectElement.FlowComputingObjectElement;

/**
 *
 * @author LukaszR
 */
public class CFMScene extends GraphPinScene< FlowComputingObjectElement, Connection, Pin >
{
    private String flowName;
    private LayerWidget backgroundLayer = new LayerWidget( this );
    //private LayerWidget mainLayer = new LayerWidget( this );
    private LayerWidget connectionLayer = new LayerWidget( this );
    private LayerWidget upperLayer = new LayerWidget( this );
    //private LayerWidget interractionLayer = new LayerWidget( this );
    public LayerWidget mainLayer = new LayerWidget( this );
    //private long edgeCounter = 0;
    public LayerWidget inter = new LayerWidget( this );
    private SceneLayout sceneLayout;
    private VMDColorScheme scheme;
    private static Logger log =
            Logger.getLogger( CFMScene.class.getName() );
    private static int edgeID = 1;
    private DCSComputingModelDataObject dataObject;
    private WidgetAction connectAction = ActionFactory.createConnectAction( inter, new SceneConnectProvider() );
    private WidgetAction reconnectAction = ActionFactory.createReconnectAction( new SceneReconnectProvider() );
    private WidgetAction moveControlPointAction = ActionFactory.createOrthogonalMoveControlPointAction();
    private WidgetAction moveAction = ActionFactory.createMoveAction();
    private Router router;
    // obluga zmiany wartosci
    private java.beans.PropertyChangeSupport propertyChangeSupport = new java.beans.PropertyChangeSupport( this );
    private Boolean isModified = new Boolean( false );
    private Long coIdTmp = new Long( 0 );

    // czy scena zostala juz zaladowana. Flaga jest dla sceneListener, zeby nie wykonywal sie przed zaladowaniem calosci.
    private boolean isSceneAllreadyLoaded = true;

    public CFMScene( VMDColorScheme scheme )
    {
        this.scheme = scheme;
        setKeyEventProcessingType( EventProcessingType.FOCUSED_WIDGET_AND_ITS_PARENTS );

        addChild( backgroundLayer );
        addChild( mainLayer );
        addChild( connectionLayer );
        addChild( upperLayer );
        this.addChild( inter );
        router = RouterFactory.createOrthogonalSearchRouter( mainLayer, connectionLayer );

        getActions().addAction( ActionFactory.createZoomAction() );
        getActions().addAction( ActionFactory.createPanAction() );
        getActions().addAction( ActionFactory.createRectangularSelectAction( this, backgroundLayer ) );

        sceneLayout = LayoutFactory.createSceneGraphLayout( this, new GridGraphLayout<FlowComputingObjectElement, Connection>().setChecker( true ) );

    }

    

    public CFMScene( String flowName )
    {
        this( VMDFactory.getOriginalScheme() );
        this.flowName = flowName;
        //this.flowModel = new FlowModel( name );
        ObjectSelectionChangedObjectSceneListener sceneListener = new ObjectSelectionChangedObjectSceneListener();
        addObjectSceneListener( sceneListener, ObjectSceneEventType.OBJECT_SELECTION_CHANGED );
        addObjectSceneListener( sceneListener, ObjectSceneEventType.OBJECT_ADDED );
        addObjectSceneListener( sceneListener, ObjectSceneEventType.OBJECT_REMOVED );
        addObjectSceneListener( sceneListener, ObjectSceneEventType.OBJECT_STATE_CHANGED );
        this.getActions().addAction( ActionFactory.createZoomAction() );
        this.getActions().addAction( ActionFactory.createPanAction() );

        ActionFactory.createMoveControlPointAction( ActionFactory.createOrthogonalMoveControlPointProvider(), ConnectionWidget.RoutingPolicy.DISABLE_ROUTING_UNTIL_END_POINT_IS_MOVED );
        getActions().addAction( ActionFactory.createCycleObjectSceneFocusAction() );
        getActions().addAction( ActionFactory.createPopupMenuAction( new ScenePopupMenu() ) );

        getActions().addAction( ActionFactory.createAcceptAction( new AcceptProvider()
        {

            @Override
            public ConnectorState isAcceptable( Widget widget, Point point, Transferable transferable )
            {
                Image dragImage = getImageFromTransferable( transferable );
                JComponent view = getView();
                Graphics2D g2 = ( Graphics2D )view.getGraphics();
                Rectangle visRect = view.getVisibleRect();
                view.paintImmediately( visRect.x, visRect.y, visRect.width, visRect.height );
                g2.drawImage( dragImage,
                        AffineTransform.getTranslateInstance( point.getLocation().getX(),
                        point.getLocation().getY() ), null );
                return ConnectorState.ACCEPT;

            }

            private Image getImageFromTransferable( Transferable transferable )
            {
                Object o = null;
                try
                {
                    o = transferable.getTransferData( DataFlavor.imageFlavor );
                }
                catch ( IOException ex )
                {
                    ex.printStackTrace();
                }
                catch ( UnsupportedFlavorException ex )
                {
                    ex.printStackTrace();
                }
                return o instanceof Image ? ( Image )o : Utilities.loadImage( "org/netbeans/shapesample/palette/shape1.png" );
            }

            @Override
            public void accept( Widget arg0, Point position, Transferable arg2 )
            {
                ModelPropertyEditor mpe = (ModelPropertyEditor)getLookup().lookup( ModelPropertyEditor.class );

                try
                {
                    Project project = FileOwnerQuery.getOwner( dataObject.getPrimaryFile() );
                    ListComputingObjectsWizardAction wizard = new ListComputingObjectsWizardAction( project );
                    ComputingObjectLoader loader = new ComputingObjectLoader();
                    if ( wizard.getSelectedComputingObject() != null )
                    {
                        // ustalenie sciezki do co
                        ProjectInformation projectInfo = ProjectUtils.getInformation( project );
                        String projectName = projectInfo.getName();
                        FileObject projectRsrc = Repository.getDefault().getDefaultFileSystem().findResource( "Templates/Project/DCS/DCS Project" );
                        String defaultProjectPkg = ( String )projectRsrc.getAttribute( "defaultProjectSrcPackage" );
                        FileObject temp = Repository.getDefault().getDefaultFileSystem().findResource( "Templates/DCS/newComputingObject" );
                        String pathToCO = "src/" + defaultProjectPkg + "/" + projectName + "/" + ( String )temp.getAttribute( "defaultPackage" ) ;
                        ComputingObject co = loader.getComputingObjectFromFile( project.getProjectDirectory() + "/" + pathToCO + "/" + wizard.getSelectedComputingObject() );
                        addComputingObjectToScene( co, position, wizard.getName(), wizard.getPathToDataDecompositor() );
                        setSceneIsModified();
                        //vcl.valueChange( "saveStatus", Boolean.TRUE, Boolean.FALSE );
                    }
                }
                catch ( DCSModelerObjectIOException ex )
                {
                    log.warning( ex.getMessage() );
                }
                catch ( IllegalArgumentException ex )
                {
                    Exceptions.printStackTrace( ex );
                }
            }
        } ) );

        addSceneListener( new SceneListener()
        {

            @Override
            public void sceneRepaint()
            {
                //log.info( "reapinted" );
            }

            @Override
            public void sceneValidating()
            {
                //throw new UnsupportedOperationException( "Not supported yet." );
            }

            @Override
            public void sceneValidated()
            {
                
                if ( isSceneAllreadyLoaded )
                {
                    
                    //setSceneIsModified();
                }
            }
        });

    }

    public void setSceneIsModified()
    {
        propertyChangeSupport.firePropertyChange( "isModified", Boolean.FALSE, new Boolean( true ) );
        isModified = true;
    }

    public void setSceneIsNotModified()
    {
        propertyChangeSupport.firePropertyChange( "isModified", isModified, new Boolean( false ) );
        isModified = true;
    }

    public void addPropertyChangeListener( String property, PropertyChangeListener listener )
    {
        propertyChangeSupport.addPropertyChangeListener( property, listener );
    }

    public void addPropertyChangeListener( PropertyChangeListener listener )
    {
        propertyChangeSupport.addPropertyChangeListener( listener );
    }

    public void removePropertyChangeListener( PropertyChangeListener listener )
    {
        propertyChangeSupport.removePropertyChangeListener( listener );
    }


    private static void createPinIn( CFMScene scene, FlowComputingObjectElement nodeID, PinIn pin )
    {
        if ( !scene.getPins().contains( pin ) )
        {
            //pin.setConnectionType( Pin.EndPointType.IN );
            CFMPinInWidget wid = ( CFMPinInWidget )( scene.addPin( nodeID, pin ) );
            String pinName = "in : ( " + pin.getType() + " ) " + pin.getName();
            wid.setProperties( pinName, null );
            //wid.getActions().addAction( scene.connectAction );
            wid.getActions().addAction( ActionFactory.createFreeMoveControlPointAction() );
        //wid.getActions().addAction( scene.reconnectAction );
        // scene.get

        }
    }

    private static void createPinOut( CFMScene scene, FlowComputingObjectElement nodeID, PinOut pin )
    {
        if ( !scene.getPins().contains( pin ) )
        {
            //pin.setConnectionType( Pin.EndPointType.OUT );
            CFMPinOutWidget wid = ( ( CFMPinOutWidget )scene.addPin( nodeID, pin ) );
            String pinName = "out : ( " + pin.getType() + " ) " + pin.getName();
            wid.setProperties( pinName, null );
            //wid.getActions().addAction( scene.connectAction );
            wid.getActions().addAction( ActionFactory.createFreeMoveControlPointAction() );
        //wid.getActions().addAction( scene.reconnectAction );
        // scene.get

        }
    }
    
    public void addComputingObjectToScene( ComputingObject co, Point position, String name, String pathToDataDecompositor )
    {
        FlowComputingObjectElement fco = new FlowComputingObjectElement();
        fco.setId( coIdTmp );
        coIdTmp++;
        fco.setName( name );
        fco.setComputingObject( co );
        try
        {
            if ( isNodeExist( fco ) )
            {
                throw new ComputingObjectExist();
            }
            else
            {
                //FlowComputingObjectElement fco = flowModel.addComputingObject( name, co );
                createComputingObjectWidget( fco, position.x, position.y );

            }
        }
        catch ( ComputingObjectExist ex )
        {
            log.warning( ex.getMessage() );
            JOptionPane.showMessageDialog( null, "Node of this name exist", "Workflow error", JOptionPane.ERROR_MESSAGE );
        }
    }

    /**
     * Przekazujemy caly dataObject, zeby posiadac nazwe otwrtego pliku, a takze moc wydobyc info
     * do jakiego projektu nalezy. Nie przekazujemy Project, bo chcemy zachowac sobie furtke do umieszczania w projekcie wielu przeplywow
     * 
     * @param dataObject
     */
    public CFMScene( DCSComputingModelDataObject dataObject )
    {
        this( dataObject.getName() );
        this.dataObject = dataObject;

    }


    public void clearScene()
    {
        this.removeChildren();
    }

    public static void clearScene( VMDGraphScene scene )
    {
        scene.removeChildren();
    }

    private void createComputingObjectWidget( FlowComputingObjectElement fco, int posX, int posY )
    {

        createComputingObjectWidget( this, fco, posX, posY );
    }

    private static void createComputingObjectWidget( CFMScene scene, FlowComputingObjectElement fco, int posX, int posY )
    {
        FlowComputingObjectElement nodeId = createNode( scene, fco, posX, posY, null );
        if ( nodeId == null )
        {
            return;
        }
        List< PinIn > pinInList = fco.getComputingObject().getPinInList();
        if ( pinInList != null )
        {
            for ( PinIn pinIn : pinInList )
            {


                pinIn.setPinID( nodeId + pinIn.getName() );
                //createPinIn( scene, nodeId, ( nodeId + entry.getKey() ), pinName, "in" );
                createPinIn( scene, nodeId, pinIn );
            }
        }
        //Iterator<Entry<String, String>> iterator = portIn.entrySet().iterator();

        List< PinOut > pinOutList = fco.getComputingObject().getPinOutList();
        //while ( iterator.hasNext() )
        if ( pinOutList != null )
        {
            for ( PinOut pinOut : pinOutList )
            {
                pinOut.setPinID( nodeId + pinOut.getName() );
                String pinName = "in : ( " + pinOut.getType() + " ) " + pinOut.getName();
                //createPinOut( scene, nodeId, ( nodeId + entry.getKey() ), pinName, "in" );
                createPinOut( scene, nodeId, pinOut );
            }
        }

    //createEdge( scene, start, end );
    }

    /**
     * Invokes layout of the scene.
     */
    public void layoutScene()
    {
        sceneLayout.invokeLayout();
    }

    public void loadSceneFromFile( String fileName )
    {
        //createNode( this, 10, 100, "first", "Table", null );
    }

    public boolean isNodeExist( FlowComputingObjectElement fco )
    {
        Collection< FlowComputingObjectElement > nodes = getNodes();
        for ( FlowComputingObjectElement fcoTmp : nodes )
        {
            if ( fcoTmp.getName().equals( fco.getName() ) )
            {
                return true;
            }
        }
        return false;
    }

    /**
     *
     * @param scene
     * @param x
     * @param y
     * @param name Nazwa FlowComputingObject
     * @param type Nazwa ComputingObject
     * @param glyphs
     * @return
     */
    private static FlowComputingObjectElement createNode( CFMScene scene, FlowComputingObjectElement fco, int x, int y, java.util.List<Image> glyphs )
    {
        //String nodeID = name;
        if ( !scene.isNodeExist( fco ) )
        {
            CFMNodeWidget widget = ( CFMNodeWidget )scene.addNode( fco );
            widget.setPreferredLocation( new Point( x, y ) );
            widget.setNodeProperties( null, fco.getName(), fco.getComputingObject().getName(), glyphs );
            widget.getActions().addAction( ActionFactory.createAlignWithMoveAction( scene.mainLayer, scene.inter, null ) );
            return fco;
        //widget.getActions().addAction( scene.connectAction );
        }
        return null;
        
    }

    /*
    private void createEdge( String sourcePinID, String targetPinID )
    {
    createEdge( this, sourcePinID, targetPinID );
    }
    
    private static void createEdge( CFMScene scene, String sourcePinID, String targetPinID )
    {
    String edgeID = "edge" + CFMScene.edgeID++;
    Widget wid = scene.addEdge( edgeID );
    System.out.println( "createEdge " + sourcePinID + "<->" + targetPinID );
    scene.setEdgeSource( edgeID, sourcePinID );
    scene.setEdgeTarget( edgeID, targetPinID );
    
    
    }*/
    private void moveTo( Point point )
    {
        int index = 0;
        for ( FlowComputingObjectElement node : getNodes() )
        {
            // node jest referencja, wiec contains() musi zadzialac poprawnie, dlatego z findNode bedzei wszystko ok
            getSceneAnimator().animatePreferredLocation( findWidget( node ), point != null ? point : new Point( ++index * 100, index * 100 ) );
        }
    }

    private Anchor getPinAnchor( Pin pin )
    {
        
        if ( pin == null )
        {
            return null;
        }
        VMDNodeWidget nodeWidget = ( VMDNodeWidget )findWidget( getPinNode( pin ) );
        Widget pinMainWidget = findWidget( pin );
        Anchor anchor;
        if ( pinMainWidget != null )
        {
            anchor = AnchorFactory.createDirectionalAnchor( pinMainWidget, AnchorFactory.DirectionalAnchorKind.HORIZONTAL, 8 );
            anchor = nodeWidget.createAnchorPin( anchor );
        }
        else
        {
            anchor = nodeWidget.getNodeAnchor();
        }
        return anchor;
    }

    public void recreateScene( SerializableFlowModel sfm )
    {
        FlowModel fm = sfm.getFlowModel();
        FlowProjectProperties properties = sfm.getProperties();
        Map< String, FlowComputingObjectElement > computingFCOs = fm.getComputingObjectList();
        Set<String> keys = computingFCOs.keySet();
        for ( String key : keys )
        {
            FlowComputingObjectElement fco = computingFCOs.get( key );
            Point pos = properties.getCoPosition( fco );
            createComputingObjectWidget( fco, pos.x , pos.y );
        }

        List< Connection > connList = fm.getConnections();
        for ( Connection conn : connList )
        {
            addEdge( conn );
            setEdgeSource( conn, conn.getStartPin() );
            setEdgeTarget( conn, conn.getEndPin() );

        }
        isSceneAllreadyLoaded = true;
    }

    public void deserialize( String val )
    {
        SerializeManager sm = new SerializeManager();
        SerializableFlowModel sfm = ( SerializableFlowModel )sm.unSerializeFromString( val );
        recreateScene( sfm );
    }

    /*
     * Tworzy obraz sceny, ktory jest zapisany, jako mapa.
     * @return Map< String,
     */
    public SerializableFlowModel getSerializableFlowModel()
    {
        
        HashMap hm = new HashMap();
        FlowProjectProperties fpp = new FlowProjectProperties();
        FlowModel flowModel = new FlowModel( this.flowName );

        // pobranie nodow i ich polozen i przepisanie do obiektu, ktory odwzorowuje scene
        Collection< FlowComputingObjectElement > nodes = getNodes();
        for ( FlowComputingObjectElement node : nodes )
        {
            flowModel.addFlowComputingObject( node.getName(), node );
            Widget widget = this.findWidget( node );
            if ( widget != null )
            {
                Point location = widget.getPreferredLocation();
                if ( location != null )
                {
                    fpp.addCoPosition( node, location );
                }
            }
        }

        // propertisy sceny. Trzymane oddzielnie, zeby mozna bylo latwo wydobyc odpowiedni obiekt do publikacji.
        // Nie chce, takze zasmiecac obiektu flowModel danymi zbednymi dla publikacji porjektu.
        Collection< Connection > edges = getEdges();
        for ( Connection conn : edges )
        {
            flowModel.addConnection( conn );
        }

        SerializableFlowModel sfm = new SerializableFlowModel( flowModel, fpp );
        return sfm;
        
    }
    
    public String serialize()
    {
        SerializeManager sm = new SerializeManager();
        return sm.serializeToString( getSerializableFlowModel() );
    }



    @Override
    protected Widget attachNodeWidget( FlowComputingObjectElement node )
    {
        CFMNodeWidget widget = new CFMNodeWidget( this );
        mainLayer.addChild( widget );

        widget.getHeader().getActions().addAction( createObjectHoverAction() );
        widget.getActions().addAction( createSelectAction() );
        widget.getActions().addAction( moveAction );

        //VMDNodeWidget widget = ( VMDNodeWidget )super.attachNodeWidget( node );
        widget.getActions().addAction( connectAction );
        return widget;
    }

    @Override
    protected Widget attachEdgeWidget( Connection edge )
    {
        VMDConnectionWidget connectionWidget = new VMDConnectionWidget( this, scheme );
        connectionWidget.setRouter( router );
        connectionLayer.addChild( connectionWidget );

        connectionWidget.getActions().addAction( createObjectHoverAction() );
        connectionWidget.getActions().addAction( createSelectAction() );
        connectionWidget.getActions().addAction( moveControlPointAction );
        connectionLayer.getActions().addAction( reconnectAction );
        return connectionWidget;
    }

    @Override
    protected Widget attachPinWidget( FlowComputingObjectElement node, Pin pin )
    {
        /*VMDPinWidget widget = ( VMDPinWidget )super.attachPinWidget( node, pin );
        widget.getActions().addAction( connectAction );
        return widget;*.
         */
        if ( pin.getPinID().endsWith( VMDGraphScene.PIN_ID_DEFAULT_SUFFIX ) )
        {
            return null;
        }
        CFMPinWidget widget = null;
        if ( pin instanceof PinIn )//.getConnectionType() == Pin.EndPointType.IN )
        {

            widget = new CFMPinInWidget( this );
            ( ( VMDNodeWidget )findWidget( node ) ).attachPinWidget( widget );

        }
        else
        {
            widget = new CFMPinOutWidget( this );
            ( ( VMDNodeWidget )findWidget( node ) ).attachPinWidget( widget );

        }
        widget.getActions().addAction( createObjectHoverAction() );
        widget.getActions().addAction( createSelectAction() );
        widget.getActions().addAction( connectAction );
        return widget;
    }

    @Override
    protected void attachEdgeSourceAnchor( Connection edge, Pin oldSourcePin, Pin sourcePin )
    {
        ( ( ConnectionWidget )findWidget( edge ) ).setSourceAnchor( getPinAnchor( sourcePin ) );
    }

    @Override
    protected void attachEdgeTargetAnchor( Connection edge, Pin oldTargetPin, Pin targetPin )
    {
        ( ( ConnectionWidget )findWidget( edge ) ).setTargetAnchor( getPinAnchor( targetPin ) );
    }


   

    private class SceneConnectProvider implements ConnectProvider
    {

        private Pin source = null;
        private Pin target = null;

        /**
         * Sprawdza czy jakis OUT jednego CO nie jest polaczony z IN 2-giego CO. Algorytm czysto rekurencyjny, a wiec molo wydajny.
         * Szczegolnie przy duzej ilosci pinow. Roazwazalem, takze sprzwdzanie dla kazdego pina OUT wszystkie polaczenia i na podstawie ich
         * dochodzenie, czy mozna dodac polaczenie, ale jest to identycznie malo wydajne. Oczywiscie w zalezonosic od scenariusza.
         * @param sourceNode
         * @param targetNode
         * @return
         */
        public boolean isAnySourceNodeOutPinConnectedToTargetNodeInPins( FlowComputingObjectElement sourceNode, FlowComputingObjectElement targetNode )
        {
            Collection< Pin > sourcePins = getNodePins( sourceNode );
            Collection< Pin > targetPins = getNodePins( targetNode );
            for ( Pin sPin : sourcePins )
            {
                if ( sPin instanceof PinOut )
                {
                    
                    for ( Pin tPin : targetPins )
                    {
                        if ( tPin instanceof PinIn )
                        {
                            Collection< Connection  > edges = findEdgesBetween( sPin, tPin );
                            if ( edges.size() > 0 )
                            {
                                
                                return true;
                            }
                        }
                    }
                }
            }
            return false;
        }

        @Override
        public boolean isSourceWidget( Widget sourceWidget )
        {
            //log.info( "poi555555555" );
            Object object = findObject( sourceWidget );
            source = ( isPin( object ) && sourceWidget instanceof CFMPinOutWidget ) ? ( Pin )object : null;
            return source != null;
        }

        @Override
        public ConnectorState isTargetWidget( Widget sourceWidget, Widget targetWidget )
        {

            Object object = findObject( targetWidget );

            target = ( isPin( object ) && ( targetWidget instanceof CFMPinInWidget ) && ( sourceWidget instanceof CFMPinOutWidget ) ) ? ( Pin )object : null;

            if ( target != null )
            {
                Collection< Connection  > edges = findPinEdges( target, false, true );
                FlowComputingObjectElement sourceNode = getPinNode( source );
                FlowComputingObjectElement targetNode = getPinNode( target );
                // ostatni warunek jest najmniej wydajny. Sprawdzany jest on tylko, kiedy pozostala, latwiejsz sie sprawdza
                // warunki:
                // - nie mozna polaczyc pinow tego samego node'a
                // - (wykomentowany, bo ten warunek nalezy do dziedziny poprzedniego) nie mozna zrobic polaczenia do tego samego pin-a
                // - jezeli da sie zrobic polaczenie pomiedzy pinami
                // - do jednego pinIn moze byc odpiety tylko jeden pinOut
                return ( !sourceNode.equals( targetNode ) /*&& !source.equals( target )*/ && ( edges.size() == 0 ) && !isAnySourceNodeOutPinConnectedToTargetNodeInPins( targetNode, sourceNode )  ) ? ConnectorState.ACCEPT : ConnectorState.REJECT_AND_STOP;
            }


            return object != null ? ConnectorState.REJECT_AND_STOP : ConnectorState.REJECT;

        }

        public boolean hasCustomTargetWidgetResolver( Scene scene )
        {

            return false;

        }

        @Override
        public Widget resolveTargetWidget( Scene scene, Point sceneLocation )
        {

            return null;

        }

        @Override
        public void createConnection( Widget sourceWidget, Widget targetWidget )
        {

            String edgeName = "edge" + CFMScene.edgeID++;
            Connection edge = new Connection( edgeName, source, target );
            addEdge( edge );
            setEdgeSource( edge, source );
            setEdgeTarget( edge, target );
        // createEdge( source, target );
        }

        /**
         * Czy probujemy podlaczyc wyjscie do tego samego node'a
         * @param targetNode Cel
         * @param sourceNode Zrodlo
         * @return boolean
         */
        private boolean isConnectedToSameNode( FlowComputingObjectElement targetNode, FlowComputingObjectElement sourceNode )
        {
            log.info(  "target: " + targetNode.getName() );
            log.info(  "source: " + sourceNode.getName() );
            if ( targetNode.equals( sourceNode ) )
            {
                return true;
            }
            return false;
        }
    }

    private class SceneReconnectProvider implements ReconnectProvider
    {

        Connection edge;
        Pin originalNode;
        Pin replacementNode;

        @Override
        public void reconnectingStarted( ConnectionWidget connectionWidget, boolean reconnectingSource )
        {
            log.info( "pupup" );
        }

        @Override
        public void reconnectingFinished( ConnectionWidget connectionWidget, boolean reconnectingSource )
        {
        }

        @Override
        public boolean isSourceReconnectable( ConnectionWidget connectionWidget )
        {

            Object object = findObject( connectionWidget );
            edge = isEdge( object ) ? ( Connection )object : null;

            originalNode = edge != null ? getEdgeSource( edge ) : null;
            log.info( "opoid" );
            return true;
        // return originalNode != null;

        }

        public boolean isTargetReconnectable( ConnectionWidget connectionWidget )
        {
            Object object = findObject( connectionWidget );

            edge = isEdge( object ) ? ( Connection )object : null;

            originalNode = edge != null ? getEdgeTarget( edge ) : null;
            log.info( "odud" );
            return originalNode != null;
        }

        public ConnectorState isReplacementWidget( ConnectionWidget connectionWidget, Widget replacementWidget, boolean reconnectingSource )
        {

            Object object = findObject( replacementWidget );
            replacementNode = isPin( object ) ? ( Pin )object : null;

            if ( replacementNode != null )
            {
                return ConnectorState.ACCEPT;
            }
            log.info( "re" );
            return object != null ? ConnectorState.REJECT_AND_STOP : ConnectorState.REJECT;

        }

        @Override
        public boolean hasCustomReplacementWidgetResolver( Scene scene )
        {
            log.info( "dsdsds" );
            return false;

        }

        @Override
        public Widget resolveReplacementWidget( Scene scene, Point sceneLocation )
        {
            log.info( "dsdsds" );
            return null;
        }

        @Override
        public void reconnect( ConnectionWidget connectionWidget, Widget replacementWidget, boolean reconnectingSource )
        {

            if ( replacementWidget == null )
            {
                removeEdge( edge );
            }
            else if ( reconnectingSource )
            {
                setEdgeSource( edge, replacementNode );
            }
            else
            {
                setEdgeTarget( edge, replacementNode );
            }

        }
    }

    private class ScenePopupMenu implements PopupMenuProvider
    {

        private JPopupMenu menu = new JPopupMenu();

        public ScenePopupMenu()
        {
            JMenuItem delete = new JMenuItem( "Delete" );
            delete.addActionListener( new ActionListener()
            {

                @Override
                public void actionPerformed( ActionEvent e )
                {
                    Set<Object> selectedObjs = ( Set<Object> )getSelectedObjects();
                    for ( Object wid : selectedObjs )
                    {
                        log.info( "Selected obj:" + wid );
                        if ( wid instanceof Pin )
                        {
                            removeNodeWithEdges( getPinNode( ( Pin )wid ) );
                        }
                        else if ( wid instanceof FlowComputingObjectElement )
                        {
                            removeNodeWithEdges( ( FlowComputingObjectElement )wid );
                        }
                        else if ( wid instanceof Connection )
                        {
                            removeEdge( ( Connection )wid );
                        }
                        //removeObject( wid );
                    }
                }
            } );
            this.menu.add( delete );
            

        }

        @Override
        public JPopupMenu getPopupMenu( Widget arg0, Point arg1 )
        {
            return menu;
        }
    }

    private class ObjectSelectionChangedObjectSceneListener implements ObjectSceneListener
    {

        @Override
        public void objectAdded( ObjectSceneEvent event, Object addedObject )
        {
            log.info( "add");
            setSceneIsModified();
        }

        @Override
        public void objectRemoved( ObjectSceneEvent event, Object removedObject )
        {
            log.info( "remove");
            setSceneIsModified();
        }

        @Override
        public void objectStateChanged( ObjectSceneEvent event, Object changedObject, ObjectState previousState, ObjectState newState )
        {
            log.info( "state changed: " + changedObject.getClass().getName());
            setSceneIsModified();
        }

        @Override
        public void selectionChanged( ObjectSceneEvent event, Set<Object> previousSelection, Set<Object> newSelection )
        {
            log.info( "zaznaczenie scene");
            Iterator it = newSelection.iterator();
            validate();
            repaint();
            if ( it.hasNext() )
            {
                Object obj = it.next();
                // sprawdzamy czy nie jest to przypadkiem pin
                if ( obj instanceof FlowComputingObjectElement )
                {
                    propertyChangeSupport.firePropertyChange( "selectedSceneComputingObject", null, obj );
                   
                }
                else if ( obj instanceof Pin )
                {
                    FlowComputingObjectElement fco = getPinNode( ( Pin )obj );
                    propertyChangeSupport.firePropertyChange( "selectedSceneComputingObject", null, fco );
                }
            }

        }

        @Override
        public void highlightingChanged( ObjectSceneEvent event, Set<Object> previousHighlighting, Set<Object> newHighlighting )
        {
            //throw new UnsupportedOperationException( "Not supported yet." );
        }

        @Override
        public void hoverChanged( ObjectSceneEvent event, Object previousHoveredObject, Object newHoveredObject )
        {
            //throw new UnsupportedOperationException( "Not supported yet." );
        }

        @Override
        public void focusChanged( ObjectSceneEvent event, Object previousFocusedObject, Object newFocusedObject )
        {
            //throw new UnsupportedOperationException( "Not supported yet." );
        }

    }
}
